#include "fsm.h"
#include "packet.h"

/*
 * use to initialize stream_state
 */
void init_stream_state(stream_state* stream, int socket_fd) {
    stream->socket_fd = socket_fd;
	stream->state = WAITING;
	stream->cseq = 0;
	stream->error = 0;
	stream->session = 0;

    // these fields are initialized when client requests SETUP
    stream->data = NULL;
    stream->play_event = NULL;
    stream->play_interval = NULL;
    stream->play_timer = NULL;
    stream->cv = NULL;
}

/*
 * called when client requests SETUP
 * initializes timer and video_manager
 * returns 0 on success, 1 on failure
 * if not able to open file, sets error = 404: File Not Found
 */
int init_timer(stream_state* stream, char* filename) {

    printf("init_timer: start\n");
    // initialize structs
    send_frame_data *data = (send_frame_data*) malloc(sizeof(send_frame_data)); // defined by me
	sig_event_t *play_event = (sig_event_t*) malloc(sizeof(sig_event_t)); // not defined by me
	timer_t *play_timer = (timer_t*) malloc(sizeof(play_timer));
	itimerspec_t *play_interval = (itimerspec_t*) malloc(sizeof(itimerspec_t)); // not defined by me
	video_manager *cv = (video_manager*) malloc(sizeof(video_manager));

    // fill out fields
	play_event->sigev_notify = SIGEV_THREAD;
	play_event->sigev_value.sival_ptr = stream;
	play_event->sigev_notify_function = send_frame;

    timer_create(CLOCK_REALTIME, play_event, play_timer);

    data->socket_fd = stream->socket_fd; // REDUNDANCY !???
    data->filename = filename;
	data->frame_num = 0;
	data->scale = 0; // will be set when client calls PLAY

	// set up cv
    cv->image = (IplImage *) malloc(sizeof(IplImage));
    cv->thumb = cvCreateMat(HEIGHT, WIDTH, CV_8UC3);
    cv->encoded = (CvMat *) malloc(sizeof(CvMat));
    cv->video = cvCaptureFromFile(filename);
    if (!cv->video) {
        // The file doesn't exist or can't be captured as a video file.
        perror("bad file");
        stream->error = 404;
        return 1;
    }
    // save in stream_state
    stream->data = data;
    stream->play_event = play_event;
    stream->play_interval = play_interval;
    stream->play_timer = play_timer;
    stream->cv = cv;

    // no errors
    printf("init_timer: end\n");
    return 0;
}

// This function will be called when the timer ticks
// TODO: complete this
void send_frame(union sigval sv_data) {

	stream_state *stream = (stream_state *) sv_data.sival_ptr;
	send_frame_data *data = stream->data;
	data->frame_num++;

	char* packet;

	//printf("sending %d to socket %d, scale of %d\n", data->frame_num, data->socket_fd, data->scale);
	// send packet
	packet = (char*) get_rtp_packet(stream->cv, data->frame_num, stream->cseq);
	// account for scaling
	while (data->frame_num % data->scale != 0) {
		free(packet);
		data->frame_num++;
		packet = (char*) get_rtp_packet(stream->cv, data->frame_num, stream->cseq);
	}

	int size = sizeof(unsigned char) * (stream->cv->encoded->cols + 16);
	//printf("Packet size: %d", size);
	if (packet) {
		int sent_bytes = size;
		if (sendall(data->socket_fd, packet, &sent_bytes) == -1)
			{
				perror("send");
			}
	}
	free(packet);
}

int sendall(int s, char *buf, int *len)
{
	int total = 0;        // how many bytes we've sent
	int bytesleft = *len; // how many we have left to send
	int n;

	while(total < *len) {
		n = send(s, buf+total, bytesleft, 0);
		if (n == -1) { break; }
		total += n;
		bytesleft -= n;
	}

	*len = total; // return number actually sent here

	return n==-1?-1:0; // return -1 on failure, 0 on success
}

// starts timer running at INTERVAL ns intervals.
void start_timer(stream_state *stream, int scale) {
    stream->data->scale = scale;

    timer_t *play_timer = stream->play_timer;
    itimerspec_t *play_interval = stream->play_interval;

    play_interval->it_interval.tv_sec = 0;
	play_interval->it_interval.tv_nsec = INTERVAL_NS;
	play_interval->it_value.tv_sec = 0;
	play_interval->it_value.tv_nsec = 1; // can't be zero
	timer_settime(*play_timer, 0, play_interval, NULL);
}

// pauses timer.
void stop_timer(stream_state *stream) {
    timer_t *play_timer = stream->play_timer;
    itimerspec_t *play_interval = stream->play_interval;

    play_interval->it_interval.tv_sec = 0;
	play_interval->it_interval.tv_nsec = 0;
	play_interval->it_value.tv_sec = 0;
	play_interval->it_value.tv_nsec = 1; // can't be zero
	timer_settime(*play_timer, 0, play_interval, NULL);
}

// removes timer and frees memory associated with it.
void delete_timer(stream_state *stream) {
    timer_t *play_timer = stream->play_timer;
    timer_delete(play_timer);
    free(stream->play_interval);
    free(stream->data);
    free(stream->play_event);
    free(stream->cv);
    stream->play_timer = NULL;
    stream->play_interval = NULL;
}

/*
 * run when request received
 */
void handle_client_request(stream_state* stream, int request_length, char* request, char* out_buffer) {
    read_request(stream, request_length, request);
    get_response(stream, out_buffer);
}

/*
 * sets error field in stream_state
 */
int check_syntax_error(stream_state* stream, char* commd_buf, char* cseq_buf, char* scale_buf, char* session_buf) {

    // check if inputs are null...
    // if null inputs, throw error 400: Bad Request
    if (!commd_buf || !cseq_buf || !session_buf) {
        return 400;
    }
    // if command is PLAY, we expect a scale header
    // if no scale header found, or scale value invalid, return error 400: Bad Request
    if (strstr (commd_buf, MSG_PLAY)) {
        if (!scale_buf) {
            return 400;
        }
        else {
            char* scale;
            scale = strpbrk(session_buf, NUMERIC);
            int s = atoi(scale);
            if (!s) {
                return 400;
            }
        }
    }
    // check cseq of client's request...
	// if no cseq, throw error 400: Bad Request
	// if cseq doesn't match, throw 451 error: Parameter Not Understood
	// example contents of cseq_buf: CSeq: 2
	char* cseq;
	cseq = strpbrk(cseq_buf, NUMERIC);
	if (!cseq) {
        return 400;
	}
	int c = atoi(cseq);
	if (stream->cseq + 1 != c) { // off by 1 because stream's session num is from previous
		return 451;
	}
	// check if client requesting SETUP...
	// example: SETUP movie.Mjpeg RTSP/1.0
	//			CSeq: 1
	// 			Transport: RTP/UDP; client_port= 25000
	if (strstr (commd_buf, MSG_SETUP)) {
		// session must have been reset by previous STOP request, or else never set
		if (stream->session != 0) {
			return 455;
		}
	} else {
		// check that session IDs match...
		// if no session ID, throw 400 error: Bad Request
		// if session ID doesn't match, throw 454 error: Session Not Found
		// example contents of session_buf: "Session: 123456"
		char* session_id;
		session_id = strpbrk(session_buf, "1234567890");
		int s = atoi(session_id);
        if (!s) {
            return 400;
        }
		if (stream->session != s) {
			return 454;
		}
	}
	// check if client is requesting valid state change
	// if invalid, throw 455 error: Method Not Allowed In This State
	if (strstr (commd_buf, MSG_SETUP)) {
		if (stream->state != WAITING) {
			return 455;
		} else {
			return 0;
		}
	} else if (strstr (commd_buf, MSG_PLAY)) {
		if (stream->state != OPEN) {
			return 455;
		} else {
			return 0;
		}
	} else if (strstr (commd_buf, MSG_PAUSE)) {
		if (stream->state != PLAYING) {
			return 455;
		} else {
			return 0;
		}
	} if (strstr (commd_buf, MSG_TEARDOWN)) {
		if (stream->state == WAITING)  {
			return 455;
		} else {
			return 0;
		}
	} else {
		// does not match any understood request
		// throw 400 error: Bad Request
		printf("Oh noes! %s", commd_buf);
		return 400;
	}
}

// return 0 on success, 1 on error
char read_request(stream_state* stream, int request_length, char* request)
{
	printf("contents of request: \n<<%s>>\n", request);

	// convert to lowercase for ease of parsing
	int i;
	for(i = 0; i<request_length; i++){
		request[i] = tolower(request[i]);
	}

	// parse to lines
	char* commd_buf;
	char* cseq_buf;
	char* scale_buf;
	char* session_buf;

	// use linebreak as delimiter
    char delims[] = "\n";

	commd_buf = strtok( request, delims );
	cseq_buf = strtok( NULL, delims );
	// commd_buf may be null if the client sent a bad message
	if (!commd_buf) {
        printf("could not find command line: exiting handle_request\n");
        return 1;
	}
	// if command is PLAY, we initialize the scale header:
	if (strstr (commd_buf, MSG_PLAY)) {
        scale_buf = strtok( NULL, delims );
	} else {
        scale_buf = NULL;
	}
	session_buf = strtok( NULL, delims );

	// check for error
	stream->error = check_syntax_error(stream, commd_buf, cseq_buf, scale_buf, session_buf);
	if (stream->error) {
		printf("received error %d: exiting handle_request\n", stream->error);
		return 1;
	}

	// no error found, safe to process client request
	if (strstr (commd_buf, MSG_SETUP)) {
		// SETUP movie.Mjpeg RTSP/1.0
		// CSeq: 1
		// Transport: RTP/UDP; client_port= 25000
		// try to get file, open file
		// may throw: 404: Not Found
		stream->state = OPEN;
		stream->session = rand() % 999999 + 1;
		// parse filename
        char* filename;
        char delim[] = " ";
        strtok( commd_buf, delim ); // captures "SETUP", ignore
        filename = strtok( NULL, delim ); // captures filename
		// open file, check for error
		if (init_timer(stream, filename)) {
            return 1;
		}

	} else if (strstr (commd_buf, MSG_PLAY)) {
		// play that funky music, white boy
		stream->state = PLAYING;
		// get first numeric token
		int scale = atoi(strpbrk(scale_buf, NUMERIC));
		start_timer(stream, scale);

	} else if (strstr (commd_buf, MSG_PAUSE)) {
		// pause video
		stream->state = OPEN;
		stop_timer(stream);

	} else { // TEARDOWN
		stream->state = WAITING;
		stream->session = 0;
		stop_timer(stream);
		delete_timer(stream);
		// TODO: close file
	}

	// because errors may be found at any stage, this is the last thing that should happen
	if (!stream->error) {
		stream->cseq++;
	}
	return 0;
}

void get_response(stream_state* stream, char* buf)
{
    int err = stream->error;
	if (err) {
		if (err == 455) {
			strcpy (buf,	"RTSP/1.0 455 Method Not Allowed in This State\n"
							"CSeq: -1\n"
							"Session: -1\n");
		} else if (err == 454) {
			strcpy (buf,	"RTSP/1.0 454 Session Not Found\n"
							"CSeq: -1\n"
							"Session: -1\n");
		} else if (err == 451) {
			strcpy (buf,	"RTSP/1.0 451 Bad CSeq\n"
							"CSeq: -1\n"
							"Session: -1\n");
		} else if (err == 404) {
			strcpy (buf,	"RTSP/1.0 451 File Not Found\n"
							"CSeq: -1\n"
							"Session: -1\n");
		} else if (err == 400) {
			strcpy (buf,	"RTSP/1.0 451 Bad Request\n"
							"CSeq: -1\n"
							"Session: -1\n");
		} else if (err == 500) {
			strcpy (buf,	"Error: Internal error\n");
		} else {
			strcpy(buf,"Error: unknown error\n");
		}
	} else {

        char intbuf[20];

        strcpy (buf,	"RTSP/1.0 200 OK\n"
                        "CSeq: ");
        sprintf (intbuf, "%d", stream->cseq);
        strcat (buf, intbuf);
        strcat (buf,	"\n"
                        "Session: ");
        sprintf (intbuf, "%d", stream->session);
        strcat (buf, intbuf);
        strcat (buf,	"\n"
                        "\n");
        }

	// print response to console
	puts(buf);
}
